#include "GameBoard.h"
#include <QDebug>
#include <cstdlib>
GameBoard::Choice::Choice(int row, int col, int rmbl, float score)
  : mRow(row), mColumn(col), mRemovedBlocks(rmbl), mScore(score) {}

bool GameBoard::Choice::operator <(const GameBoard::Choice& rhs) const
{
  return mScore < rhs.mScore;
}


QColor GameBoard::colorFromTileColor(GameBoard::TILE_COLOR color)
{
  switch(color)
  {
    case GameBoard::BLACK: return QColor(0,0,0); break;
    case GameBoard::RED: return QColor(255,100,100); break;
    case GameBoard::GREEN: return QColor(100,255,100); break;
    case GameBoard::BLUE: return QColor(100,100,255); break;
    case GameBoard::ORANGE: return QColor(255,128,64); break;
    case GameBoard::YELLOW: return QColor(255,255,100); break;
    case GameBoard::MAGENTA: return QColor(255,100,255); break;
    case GameBoard::CYAN: return QColor(100,255,255); break;
    default: return QColor(-1,-1,-1); break;
  }
}

GameBoard::GameBoard(int width, int height, int numColors)
  : QObject(NULL),
    mLock(),
    mWidth(width),
    mHeight(height),
    mNumColors(numColors<GameBoard::MAX_TILE_COLOR?numColors:MAX_TILE_COLOR-1)
{
  generate();
}

GameBoard::GameBoard(const GameBoard &copy)
  : QObject(NULL),
    mLock(),
    mWidth(copy.mWidth),
    mHeight(copy.mHeight),
    mNumColors(copy.mNumColors)
{
  mLock.lock();
  mTiles.reserve(mHeight);
  for (int r=0; r<mHeight; ++r)
  {
    mTiles.push_back(QList<GameBoard::TILE_COLOR>());
    mTiles.back().reserve(mWidth);
    for (int c=0; c<mWidth; ++c)
    {
      mTiles.back().push_back(copy.mTiles.at(r).at(c));
    }
  }
  emit modified();
  mLock.unlock();
}

const GameBoard& GameBoard::operator=(const GameBoard& rhs)
{
  mLock.lock();
  mTiles.clear();
  mWidth = rhs.mWidth;
  mHeight = rhs.mHeight;
  mNumColors = rhs.mNumColors;
  mTiles.reserve(mHeight);
  for (int r=0; r<mHeight; ++r)
  {
    mTiles.push_back(QList<GameBoard::TILE_COLOR>());
    mTiles.back().reserve(mWidth);
    for (int c=0; c<mWidth; ++c)
    {
      mTiles.back().push_back(rhs.mTiles.at(r).at(c));
    }
  }
  emit modified();
  mLock.unlock();
  return *this;
}

GameBoard::~GameBoard() {}

void GameBoard::lock() { mLock.lock(); }
void GameBoard::unlock() { mLock.unlock(); }
int GameBoard::rows() const { return mHeight; }
int GameBoard::cols() const { return mWidth; }

const QList< QList<GameBoard::TILE_COLOR> >& GameBoard::getBoard() const
{
  return mTiles;
}

void GameBoard::generate()
{
  mLock.lock();
  mTiles.clear();
  mTiles.reserve(mHeight);
  for (int r=0; r<mHeight; ++r)
  {
    mTiles.push_back(QList<GameBoard::TILE_COLOR>());
    mTiles.back().reserve(mWidth);
    for (int c=0; c<mWidth; ++c)
    {
      mTiles.back().push_back((GameBoard::TILE_COLOR)(rand()%mNumColors));
    }
  }
  emit modified();
  mLock.unlock();
}

void GameBoard::generate(int width, int height, int numColors)
{
  mWidth = width;
  mHeight = height;
  mNumColors = numColors;
  generate();
}

void GameBoard::load(const QString &file)
{
}

bool GameBoard::isValidPosition(const QPoint& pos) const
{
  return pos.x()>=0 && pos.x()<mHeight &&
         pos.y()>=0 && pos.y()<mWidth;
}

unsigned int GameBoard::clickAt(const QPoint& pos)
{
  if (!isValidPosition(pos) || mTiles[pos.x()][pos.y()] == GameBoard::BLACK) return getRemainingTiles();
  if ((isValidPosition(QPoint(pos.x()-1,pos.y())) &&
         mTiles.at(pos.x()-1).at(pos.y()) == mTiles.at(pos.x()).at(pos.y())) ||
      (isValidPosition(QPoint(pos.x()+1,pos.y())) &&
         mTiles.at(pos.x()+1).at(pos.y()) == mTiles.at(pos.x()).at(pos.y())) ||
      (isValidPosition(QPoint(pos.x(),pos.y()-1)) &&
         mTiles.at(pos.x())  .at(pos.y()-1) == mTiles.at(pos.x()).at(pos.y())) ||
      (isValidPosition(QPoint(pos.x(),pos.y()+1)) &&
         mTiles.at(pos.x())  .at(pos.y()+1) == mTiles.at(pos.x()).at(pos.y())))
  {
    unsigned int size = 1;
    removeSameNeighbors(QPoint(pos.x()-1, pos.y()+0), mTiles.at(pos.x()).at(pos.y()), size);
    removeSameNeighbors(QPoint(pos.x()+1, pos.y()+0), mTiles.at(pos.x()).at(pos.y()), size);
    removeSameNeighbors(QPoint(pos.x()+0, pos.y()+1), mTiles.at(pos.x()).at(pos.y()), size);
    removeSameNeighbors(QPoint(pos.x()+0, pos.y()-1), mTiles.at(pos.x()).at(pos.y()), size);
    mTiles[pos.x()][pos.y()] = GameBoard::BLACK;
    repositionTiles();
    emit modified();
  }
  checkForLose();
  return getRemainingTiles();
}

void GameBoard::removeSameNeighbors(const QPoint &pos, TILE_COLOR col, unsigned int &size)
{
  if (!isValidPosition(pos) || mTiles.at(pos.x()).at(pos.y()) == GameBoard::BLACK) return;
  if (mTiles.at(pos.x()).at(pos.y()) == col)
  {
    mLock.lock();
    mTiles[pos.x()][pos.y()] = GameBoard::BLACK;
    mLock.unlock();
    ++size;
    removeSameNeighbors(QPoint(pos.x()-1, pos.y()+0), col, size);
    removeSameNeighbors(QPoint(pos.x()+1, pos.y()+0), col, size);
    removeSameNeighbors(QPoint(pos.x()+0, pos.y()+1), col, size);
    removeSameNeighbors(QPoint(pos.x()+0, pos.y()-1), col, size);
  }
}
bool GameBoard::allAboveBlack(const QPoint &pos) const
{
  int row(pos.y());
  while (row<mWidth)
  {
    if (mTiles[pos.x()][row] != GameBoard::BLACK)
      return false;
    ++row;
  }

  return true;
}

bool GameBoard::allRightBlack(const QPoint &pos) const
{
  int col(pos.x());
  while (col<mHeight)
  {
    if (mTiles[col][pos.y()] != GameBoard::BLACK)
      return false;
    ++col;
  }

  return true;
}

void GameBoard::repositionTiles()
{
  // Make tiles fall
  for (int c=0; c<mHeight; ++c)
  {
    for (int r=mWidth-2; r>=0; --r)
    {
      // If a tile is black do a downshift to all tiles above it and make the top tile black
      if (mTiles[c][r] == GameBoard::BLACK && !allAboveBlack(QPoint(c,r)))
      {
        mLock.lock();
        for (int i=r; i<mWidth-1; ++i)
          mTiles[c][i] = mTiles[c][i+1];
        mTiles[c][mWidth-1] = GameBoard::BLACK;
        mLock.unlock();
      }
    }
  }

  // Left Shift
  for (int c=0; c<mHeight-1; ++c)
  {
    // If the bottom tile in a column is black, it is assumed that there is nothing
    // in the column (because of the falling property)
    if (mTiles[c][0] == GameBoard::BLACK && !allRightBlack(QPoint(c,0)))
    {
      // Do a count of the length of the gap.
      int len = 0;
      while (c+len<mHeight-1&& mTiles[c+len][0] == GameBoard::BLACK) ++len;
      for (int a=0; a<len; ++a)
      {
        for (int r=0; r<mWidth; ++r)
        {
          mLock.lock();
          for (int i=c; i<mHeight-1;++i)
            mTiles[i][r] = mTiles[i+1][r];
          mTiles[mHeight-1][r] = GameBoard::BLACK;
          mLock.unlock();
        }
      }
    }
  }
}

bool GameBoard::checkForLose()
{
  if (getNumSingletons() == getRemainingTiles())
  {
    emit lose();
    return true;
  }
  else
  {
    for (int i=0; i<mNumColors; ++i)
    {
      if (getRemainingTiles((TILE_COLOR)i) == 1)
      {
        emit lose();
        return true;
      }
    }
    return false;
  }
}

unsigned int GameBoard::getRemainingTiles()
{
  unsigned int count = 0;
  foreach(QList<GameBoard::TILE_COLOR> r, mTiles)
    foreach(GameBoard::TILE_COLOR t, r)
      if (t != GameBoard::BLACK) ++count;

  if (count <= 0) emit win();
  return count;
}

unsigned int GameBoard::getRemainingTiles(GameBoard::TILE_COLOR col)
{
  unsigned int count = 0;
  foreach(QList<GameBoard::TILE_COLOR> r, mTiles)
    foreach(GameBoard::TILE_COLOR t, r)
      if (t == col) ++count;
  return count;
}

unsigned int GameBoard::getNumSingletons()
{
  unsigned int count = 0;
  for (int c=0; c<mHeight; ++c)
    for(int r=0; r<mWidth; ++r)
    {
      bool isSingleton = mTiles[c][r] != GameBoard::BLACK;
      if (isValidPosition(QPoint(c-1,r))) isSingleton &= mTiles[c-1][r] != mTiles[c][r];
      if (isValidPosition(QPoint(c+1,r))) isSingleton &= mTiles[c+1][r] != mTiles[c][r];
      if (isValidPosition(QPoint(c,r-1))) isSingleton &= mTiles[c][r-1] != mTiles[c][r];
      if (isValidPosition(QPoint(c,r+1))) isSingleton &= mTiles[c][r+1] != mTiles[c][r];

      if (isSingleton) ++count;
    }
  return count;
}

QList<GameBoard::Choice> GameBoard::getChoiceList() const
{
  QList<GameBoard::Choice> choices;
  GameBoard tb(*this);
  for (int c=tb.mHeight-1; c>=0; --c)
  {
    for (int r=tb.mWidth-1; r>=0; --r)
    {
      QPoint pos(c,r);
      if (!tb.isValidPosition(pos) || tb.mTiles[pos.x()][pos.y()] != GameBoard::BLACK)
      {
        if ((tb.isValidPosition(QPoint(pos.x()-1,pos.y())) &&
               tb.mTiles.at(pos.x()-1).at(pos.y()) == tb.mTiles.at(pos.x()).at(pos.y())) ||
            (tb.isValidPosition(QPoint(pos.x()+1,pos.y())) &&
               tb.mTiles.at(pos.x()+1).at(pos.y()) == tb.mTiles.at(pos.x()).at(pos.y())) ||
            (tb.isValidPosition(QPoint(pos.x(),pos.y()-1)) &&
               tb.mTiles.at(pos.x())  .at(pos.y()-1) ==tb. mTiles.at(pos.x()).at(pos.y())) ||
            (tb.isValidPosition(QPoint(pos.x(),pos.y()+1)) &&
               tb.mTiles.at(pos.x())  .at(pos.y()+1) == tb.mTiles.at(pos.x()).at(pos.y())))
        {
          unsigned int size = 1;
          tb.removeSameNeighbors(QPoint(pos.x()-1, pos.y()+0), tb.mTiles.at(pos.x()).at(pos.y()), size);
          tb.removeSameNeighbors(QPoint(pos.x()+1, pos.y()+0), tb.mTiles.at(pos.x()).at(pos.y()), size);
          tb.removeSameNeighbors(QPoint(pos.x()+0, pos.y()+1), tb.mTiles.at(pos.x()).at(pos.y()), size);
          tb.removeSameNeighbors(QPoint(pos.x()+0, pos.y()-1), tb.mTiles.at(pos.x()).at(pos.y()), size);
          choices.push_back(GameBoard::Choice(r, c, size));
        }
      }
    }
  }
  return choices;
}
